//===- LLVMDialectBytecode.cpp - LLVM Bytecode Implementation -------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "LLVMDialectBytecode.h"
#include "mlir/Bytecode/BytecodeImplementation.h"
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
#include "mlir/IR/Diagnostics.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/TypeSwitch.h"
#include <type_traits>

using namespace mlir;
using namespace mlir::LLVM;

namespace {

// Provide some forward declarations of the functions that will be generated by
// the include below.
static void write(DIExpressionElemAttr attribute,
                  DialectBytecodeWriter &writer);
static LogicalResult writeAttribute(Attribute attribute,
                                    DialectBytecodeWriter &writer);

//===--------------------------------------------------------------------===//
// Optional ArrayRefs
//
// Note that both the writer and reader functions consider attributes to be
// optional. This is because the attribute may be present or empty.
//===--------------------------------------------------------------------===//

template <class EntryTy>
static void writeOptionalArrayRef(DialectBytecodeWriter &writer,
                                  ArrayRef<EntryTy> storage) {
  if (storage.empty()) {
    writer.writeOwnedBool(false);
    return;
  }

  writer.writeOwnedBool(true);
  writer.writeList(storage, [&](EntryTy val) {
    if constexpr (std::is_base_of_v<Attribute, EntryTy>) {
      (void)writer.writeOptionalAttribute(val);
    } else if constexpr (std::is_integral_v<EntryTy>) {
      (void)writer.writeVarInt(val);
    } else {
      static_assert(true, "EntryTy not supported");
    }
  });
}

template <class EntryTy>
static LogicalResult readOptionalArrayRef(DialectBytecodeReader &reader,
                                          SmallVectorImpl<EntryTy> &storage) {
  bool isPresent = false;
  if (failed(reader.readBool(isPresent)))
    return failure();
  // Nothing to do here, the array is empty.
  if (!isPresent)
    return success();

  auto readEntry = [&]() -> FailureOr<EntryTy> {
    EntryTy temp;
    if constexpr (std::is_base_of_v<Attribute, EntryTy>) {
      if (succeeded(reader.readOptionalAttribute(temp)))
        return temp;
    } else if constexpr (std::is_integral_v<EntryTy>) {
      if (succeeded(reader.readVarInt(temp)))
        return temp;
    } else {
      static_assert(true, "EntryTy not supported");
    }
    return failure();
  };

  return reader.readList(storage, readEntry);
}

//===--------------------------------------------------------------------===//
// Optional integral types
//===--------------------------------------------------------------------===//

template <class EntryTy>
static void writeOptionalInt(DialectBytecodeWriter &writer,
                             std::optional<EntryTy> storage) {
  static_assert(std::is_integral_v<EntryTy>,
                "EntryTy must be an integral type");
  EntryTy val = storage.value_or(0);
  writer.writeVarIntWithFlag(val, storage.has_value());
}

template <class EntryTy>
static LogicalResult readOptionalInt(DialectBytecodeReader &reader,
                                     std::optional<EntryTy> &storage) {
  static_assert(std::is_integral_v<EntryTy>,
                "EntryTy must be an integral type");
  uint64_t result = 0;
  bool flag = false;
  if (failed(reader.readVarIntWithFlag(result, flag)))
    return failure();
  if (flag)
    storage = static_cast<EntryTy>(result);
  else
    storage = std::nullopt;
  return success();
}

//===--------------------------------------------------------------------===//
// Tablegen generated bytecode functions
//===--------------------------------------------------------------------===//

#include "mlir/Dialect/LLVMIR/LLVMDialectBytecode.cpp.inc"

//===--------------------------------------------------------------------===//
// LLVMDialectBytecodeInterface
//===--------------------------------------------------------------------===//

/// This class implements the bytecode interface for the LLVM dialect.
struct LLVMDialectBytecodeInterface : public BytecodeDialectInterface {
  LLVMDialectBytecodeInterface(Dialect *dialect)
      : BytecodeDialectInterface(dialect) {}

  // Attributes
  Attribute readAttribute(DialectBytecodeReader &reader) const override {
    return ::readAttribute(getContext(), reader);
  }

  LogicalResult writeAttribute(Attribute attr,
                               DialectBytecodeWriter &writer) const override {
    return ::writeAttribute(attr, writer);
  }

  // Types
  Type readType(DialectBytecodeReader &reader) const override {
    return ::readType(getContext(), reader);
  }

  LogicalResult writeType(Type type,
                          DialectBytecodeWriter &writer) const override {
    return ::writeType(type, writer);
  }
};
} // namespace

void LLVM::detail::addBytecodeInterface(LLVMDialect *dialect) {
  dialect->addInterfaces<LLVMDialectBytecodeInterface>();
}
